package org.zcl.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.zcl.entity.SeckillProducts;
import org.zcl.model.stock.StockRequest;
import org.zcl.service.SeckillProductsService;
import org.zcl.service.StockCallback;
import org.zcl.service.StockService;
import org.zcl.utils.CacheUtil;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 扣库存
 *
 * @author yuhao.wang
 */
@Service
@Slf4j
public class StockServiceImpl implements StockService {

    /**
     * Redis 客户端
     */
    @Autowired
    private CacheUtil cacheUtil;

    @Autowired
    private SeckillProductsService seckillProductsService;

    @Autowired
    private StockCallback stockCallback;

    /**
     * @param key           库存key
     * @param expire        库存有效时间,单位秒
     * @param num           扣减数量
     * @return -2:库存不足; -1:不限库存; 大于等于0:扣减库存之后的剩余库存
     */
    @Override
    public Boolean stock(StockRequest stockRequest, String key, long expire, int num) {
        // 初始化库存
        try {
            // 获取锁
            cacheUtil.tryLock("stock" + key, 1, processor -> {
                Integer stock = stock(key, num);
                // 双重验证，避免并发时重复回源到数据库
                if (stock == null || stock < 0) {
                    // 调一次扣库存的操作
                    stock = stock(key, num);
                }
                if (stock > 0) {
                    try {
                        log.info("发消息同步库存到mysql");
                        if (num / 2 == 0) {
                            int i = 1 / 0;
                        }
                        SeckillProducts products = seckillProductsService.stock(stockRequest);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                        recovery(key, num);
                        return false;
                    }
                }
                return stock;
            });
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    @Override
    public SeckillProducts init(Long productId, String key, long expire) {
        // 获取初始化库存
        Integer initStock = stockCallback.getStockByDB(productId);
        // 将库存设置到redis
        cacheUtil.set(key, initStock, expire, TimeUnit.SECONDS);
        Integer stock = cacheUtil.get(key);
        log.info("redis init productId:{},stock:{}", productId, stock);
        return seckillProductsService.getById(productId);
    }

    /**
     * 获取初始的库存
     *
     * @return
     */
    @Override
    public int initStock(long commodityId) {
        // 这里做一些初始化库存的操作
        SeckillProducts products = seckillProductsService.getById(commodityId);
        seckillProductsService.updateById(products);
        return products.getStock();
    }

    private SeckillProducts dbStock(String productId, int num) {
        // 检查库存是否足够
        SeckillProducts product = seckillProductsService.getById(productId);
        if (product == null || product.getStock() <= 0) {
            return null;
        }

        // 扣减库存
        product.setStock(product.getStock() - 1);
        seckillProductsService.updateById(product);
        return product;
    }

    /**
     * 加库存(还原库存)
     *
     * @param key 库存key
     * @param num 库存数量
     * @return
     */
    @Override
    public long addStock(String key, int num) {
        return addStock(key, null, num);
    }

    /**
     * 加库存
     *
     * @param key    库存key
     * @param expire 过期时间（秒）
     * @param num    库存数量
     * @return
     */
    @Override
    public long addStock(String key, Long expire, int num) {
        AtomicBoolean hasKey = new AtomicBoolean(cacheUtil.hasKey(key));
        // 判断key是否存在，存在就直接更新
        if (hasKey.get()) {
            return cacheUtil.increment(key, num);
        }

        Assert.notNull(expire, "初始化库存失败，库存过期时间不能为null");
        try {
            cacheUtil.tryLock("stock", 1, processor -> {
                // 获取到锁后再次判断一下是否有key
                hasKey.set(cacheUtil.hasKey(key));
                if (!hasKey.get()) {
                    // 初始化库存
                    cacheUtil.set(key, num, expire, TimeUnit.SECONDS);
                }
            });
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return num;
    }

    /**
     * 获取库存
     *
     * @param key 库存key
     * @return -1:不限库存; 大于等于0:剩余库存
     */
    @Override
    public int getStock(String key) {
        Integer stock = cacheUtil.getSerial(key);
        return stock == null ? -1 : stock;
    }

    /**
     * 扣库存
     *
     * @param key 库存key
     * @param num 扣减库存数量
     */
    private Integer stock(String key, int num) {
        return cacheUtil.decreaseStock(key, (long) num);
    }

    /**
     * 恢复库存
     *
     * @param key 库存key
     * @param num 扣减库存数量
     */
    private Integer recovery(String key, int num) {
        return cacheUtil.recovery(key, (long) num);
    }

}
